import os
import chardet
import time
import docx
import re
import numpy as np
from typing import List, Dict, Tuple, Any
from openai import OpenAI
from collections import Counter
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity


class SmartExamBot:
    def __init__(self, api_key: str, base_url: str, max_questions: int = 5):
        """初始化考试机器人"""
        self.api_key = api_key
        self.base_url = base_url
        self.client = OpenAI(
            api_key=api_key,
            base_url=base_url
        )
        self.max_follow_ups = 0
        self.question_count = 0
        self.total_score = 0
        self.max_possible_score = 0
        self.max_questions = max_questions
        self.current_follow_ups = 0
        self.question_scores = []
        self.cannot_answer_patterns = re.compile(
            r'不会|不知道|不清楚|没学过|不懂|不了解|不会做|不知道怎么答|无法回答|我举不出来例子|分析不出来',
            re.IGNORECASE
        )
        self.irrelevant_patterns = re.compile(
            r'跑题|偏题|无关|答非所问|换个题|另一个|不相关|说点别的|其他内容|别的话题',
            re.IGNORECASE
        )
        self.image_patterns = re.compile(
            r'如图|图示|图片|图表|图[.\d]+|figure|graph|chart|diagram|illustration|源程序',
            re.IGNORECASE
        )
        self.knowledge_content = ""
        self.keywords = []
        self.current_question = ""
        self.question_dimensions = []
        self.unrelated_topics = {'天气', '吃饭', '电影', '游戏', '睡觉', '时间', '日期',
                                 '音乐', '歌曲', '明星', '电影', '旅游', '购物', '美食',
                                 '体育', '运动', '小说', '书籍', '电视剧', '综艺'}

        # RAG相关初始化
        self.vectorizer = TfidfVectorizer()
        self.knowledge_vectors = None
        self.knowledge_passages = []

        # 新增：历史题目记录
        self.generated_questions = []
        self.question_similarity_threshold = 0.7  # 题目相似度阈值

    def _chunk_knowledge(self, content: str, chunk_size: int = 500) -> List[str]:
        """将知识库内容分块处理"""
        chunks = []
        words = content.split()
        for i in range(0, len(words), chunk_size):
            chunk = " ".join(words[i:i + chunk_size])
            chunks.append(chunk)
        return chunks

    def _vectorize_knowledge(self):
        """向量化知识库内容"""
        if not self.knowledge_passages:
            self.knowledge_passages = self._chunk_knowledge(self.knowledge_content)
        self.vectorizer.fit(self.knowledge_passages)
        self.knowledge_vectors = self.vectorizer.transform(self.knowledge_passages)

    def retrieve_relevant_passages(self, query: str, top_k: int = 3) -> List[str]:
        """检索与查询最相关的知识库片段"""
        if self.knowledge_vectors is None:
            self._vectorize_knowledge()

        query_vec = self.vectorizer.transform([query])
        similarities = cosine_similarity(query_vec, self.knowledge_vectors)
        top_indices = np.argsort(similarities[0])[-top_k:][::-1]

        return [self.knowledge_passages[i] for i in top_indices]

    def _is_question_too_similar(self, new_question: str) -> bool:
        """检查新题目是否与历史题目过于相似"""
        if not self.generated_questions:
            return False

        # 向量化新题目
        new_vec = self.vectorizer.transform([new_question])

        # 计算与所有历史题目的相似度
        for old_question in self.generated_questions:
            old_vec = self.vectorizer.transform([old_question])
            similarity = cosine_similarity(new_vec, old_vec)[0][0]
            if similarity > self.question_similarity_threshold:
                return True
        return False

    def _extract_keywords(self, text: str) -> List[str]:
        """从知识库中提取关键词"""
        words = re.findall(r'\b[\u4e00-\u9fa5]{2,6}\b', text)
        word_counts = Counter(words)
        stop_words = {'可以', '一个', '这个', '我们', '进行', '需要', '应该', '问题', '方法'}
        keywords = [word for word, count in word_counts.most_common(20)
                    if word not in stop_words and count > 1]
        return keywords[:10] if keywords else ["数据", "分析", "技术"]

    def _extract_dimensions(self, question: str) -> List[str]:
        """提取题目要求的分析维度"""
        match = re.search(r'请从(.*?)分析', question)
        if match:
            dimensions_text = match.group(1)
            dimensions = [dim.strip() for dim in re.split(r'、|和|与', dimensions_text) if dim.strip()]
            return dimensions
        return []

    def _is_duplicating_question(self, question: str, answer: str) -> bool:
        """检测回答是否只是重复问题内容"""
        if not question or not answer:
            return False

        def preprocess(text):
            text = re.sub(r'[^\w\s]', '', text)
            text = re.sub(r'请从|分析|简述|说明|论述|谈谈|如何|什么|为什么|是什么|有哪些', '', text,
                          flags=re.IGNORECASE)
            return text.strip()

        processed_question = preprocess(question)
        processed_answer = preprocess(answer)

        if len(processed_answer) < len(processed_question) * 0.5:
            return True

        common_chars = 0
        min_len = min(len(processed_question), len(processed_answer))
        for i in range(min_len):
            if processed_question[i] == processed_answer[i]:
                common_chars += 1

        if common_chars / min_len > 0.3:
            return True

        question_words = set(re.findall(r'\b[\u4e00-\u9fa5a-zA-Z]{2,}\b', processed_question))
        answer_words = set(re.findall(r'\b[\u4e00-\u9fa5a-zA-Z]{2,}\b', processed_answer))

        if question_words and len(answer_words.intersection(question_words)) / len(question_words) > 0.7:
            return True

        return True if processed_answer in processed_question or processed_question in processed_answer else False

    def detect_encoding(self, file_path: str) -> str:
        """检测文件编码"""
        if file_path.lower().endswith('.docx'):
            return 'docx'
        try:
            with open(file_path, 'rb') as file:
                result = chardet.detect(file.read(10240))
                return result['encoding'] or 'utf-8'
        except Exception as e:
            raise Exception(f"编码检测失败: {str(e)}")

    def read_file_content(self, file_path: str) -> str:
        """读取文件内容，支持txt和docx格式"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")

        if not os.path.isfile(file_path):
            raise IsADirectoryError(f"{file_path}不是一个文件")

        try:
            encoding = self.detect_encoding(file_path)

            if encoding == 'docx':
                doc = docx.Document(file_path)
                full_text = []
                for para in doc.paragraphs:
                    full_text.append(para.text)
                for table in doc.tables:
                    for row in table.rows:
                        row_text = [cell.text for cell in row.cells]
                        full_text.append('\t'.join(row_text))
                return '\n'.join(full_text)

            encodings_to_try = [encoding, 'utf-8', 'gbk', 'latin-1']
            for enc in encodings_to_try:
                try:
                    with open(file_path, 'r', encoding=enc, errors='replace') as file:
                        return file.read()
                except UnicodeDecodeError:
                    continue
            raise Exception("无法识别文件编码")
        except Exception as e:
            raise Exception(f"文件读取失败: {str(e)}")

    def get_ai_response(self, messages: List[Dict], max_retries: int = 3) -> str:
        """获取AI响应，带重试机制"""
        retry_count = 0
        while retry_count < max_retries:
            try:
                response = self.client.chat.completions.create(
                    model="deepseek-v3",
                    messages=messages,
                    temperature=0.7,  # 提高温度值以增加多样性
                    timeout=30
                )
                return response.choices[0].message.content
            except Exception as e:
                retry_count += 1
                if retry_count >= max_retries:
                    raise Exception(f"API请求失败: {str(e)}")
                print(f"API调用失败，正在重试({retry_count}/{max_retries})...")
                time.sleep(2)

    def is_cannot_answer(self, user_input: str) -> bool:
        """判断用户输入是否表示'不会'"""
        user_input = user_input.strip()
        if not user_input or re.fullmatch(r'[\s,.!?;]+', user_input):
            return True
        return self.cannot_answer_patterns.search(user_input) is not None

    def is_irrelevant(self, user_answer: str) -> Tuple[bool, str]:
        """判断回答是否与题目和知识库无关（乱答判定）"""
        user_answer = user_answer.strip().lower()

        if self.irrelevant_patterns.search(user_answer):
            return True, "回答明确表示与题目无关"

        question_keywords = re.findall(r'\b[\u4e00-\u9fa5]{2,6}\b', self.current_question)
        all_keywords = question_keywords + self.keywords + self.question_dimensions

        has_relevant_keywords = any(keyword in user_answer for keyword in all_keywords)

        if not has_relevant_keywords:
            for topic in self.unrelated_topics:
                if topic in user_answer:
                    return True, f"回答涉及无关主题({topic})，视为乱答"

            if len(user_answer) < 5:
                return True, "回答过短且不涉及题目相关内容，视为乱答"

            return True, "回答与题目及知识库内容无关，视为乱答"

        return False, ""

    def start_exam(self, knowledge_content: str):
        """开始考试流程"""
        self.knowledge_content = knowledge_content
        self.keywords = self._extract_keywords(knowledge_content)
        self._vectorize_knowledge()

        print("\n" + "=" * 50)
        print("智能考试机器人".center(40))
        print("=" * 50)
        print("\n我将根据提供的知识内容对你进行考察。")
        print(f"考试共{self.max_questions}题。")
        print("输入'退出'可随时结束考试。")
        print("注意：可以输入多行内容，以空行结束输入\n")

        messages = [
            {"role": "system", "content": f"""
            你是一个公平的智能考试机器人，需要基于以下知识库内容出题和评分：
            {self.knowledge_content[:2000]}...

            工作模式：

            1. 题目生成模式：
            - 必须从知识库中提取具体概念出题
            - 题目必须包含至少1个关键词：{', '.join(self.keywords)}
            - 必须要求从1-2个维度回答
            - 示例："大语言模型在学生学业中正面作用是什么?它可能带来什么危害?学生如何避免"
            - 只返回题目，不要包含评分信息、评分标准等其他任何信息
            - 绝对不能包含任何图片、图示相关的内容
            - 题目必须多样化，避免与已出题目相似
            - 不要给出回答建议
            - 尽量从不同角度提问
            """},
        ]

        while self.question_count < self.max_questions:
            self._handle_new_question(messages)
            print("\n你: ", end="", flush=True)
            user_input_lines = []
            while True:
                try:
                    line = input().strip()
                except EOFError:
                    line = "退出"

                if line.lower() in ['退出', 'q', 'quit']:
                    self._show_final_score()
                    return

                if not line and user_input_lines:
                    user_input = '\n'.join(user_input_lines)
                    break

                if line:
                    user_input_lines.append(line)
                elif not user_input_lines:
                    print("请输入内容或命令：", end="", flush=True)
                    continue

            self._handle_user_answer(user_input, messages)
            print(f"\n本题结束，当前进度: {self.question_count}/{self.max_questions}题")

        self._show_final_score()

    def _handle_new_question(self, messages: List[Dict]):
        """题目生成逻辑 - RAG增强版"""
        self.question_count += 1
        max_retries = 5
        retry_count = 0

        while retry_count < max_retries:
            print(f"\n===== 第{self.question_count}/{self.max_questions}题 =====")

            # RAG增强：检索与关键词最相关的知识片段
            context_query = " ".join(self.keywords[:3])
            relevant_passages = self.retrieve_relevant_passages(context_query, top_k=2)
            context = "\n\n".join(relevant_passages)

            prompt = f"""请基于以下知识库内容出一道新颖的主观题（避免与已出题目相似）：
            知识库相关段落：
            {context[:1500]}

            要求：
            1. 必须从以下关键词中选择至少1个：{', '.join(self.keywords)}
            2. 题目形式应为：
               "请从[维度1]分析[关键词]..."
            3. 确保题目在知识库中有明确答案依据
            4. 题目必须与前{len(self.generated_questions)}道已出题目有明显区别
            5. 从不同角度提问（如：定义、应用、优缺点、案例分析等）
            6. 仅输出题目内容，不要输出“评分标准”等无关内容
"""

            messages.append({"role": "user", "content": prompt})
            question = self.get_ai_response(messages)

            # 验证题目质量
            if not any(keyword in question for keyword in self.keywords):
                print("⚠️ 题目未包含关键词，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            if len(re.findall(r'维度|角度|方面', question)) < 1:
                print("⚠️ 题目未明确要求多维度回答，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            if self.image_patterns.search(question):
                print("⚠️ 题目包含图片相关表述，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            question = question.split("|")[0].strip()
            question = re.sub(r'^\d+[.\d]*\s*', '', question)

            if not question or len(question) < 10:
                print("⚠️ 题目过短，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            # 检查题目相似度
            if self._is_question_too_similar(question):
                print("⚠️ 题目与历史题目过于相似，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            # 保存当前题目和提取的维度
            self.current_question = question
            self.question_dimensions = self._extract_dimensions(question)
            self.generated_questions.append(question)  # 记录已生成题目
            messages.append({"role": "assistant", "content": question})
            print(f"考试机器人: {question}")
            self.max_possible_score += 10
            break
        else:
            print("⚠️ 多次尝试后仍无法获取有效题目，跳过本题")
            self.question_count -= 1
            return

    def _handle_user_answer(self, user_answer: str, messages: List[Dict]):
        """优化的评分逻辑 - RAG增强版"""
        if self.is_cannot_answer(user_answer):
            print(f"\n用户回答: {user_answer}")
            print("\n检测到无法回答的表述，按规则得0分")
            self.total_score += 0
            self.question_scores.append(0)
            print(f"\n评分: 0.0/10")
            print(f"反馈: 本题需要按照题目要求的多个维度回答")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")
            messages.append({"role": "user", "content": user_answer})
            messages.append({"role": "assistant", "content": "0|检测到无法回答的表述|否"})
            return

        is_irrelevant, reason = self.is_irrelevant(user_answer)
        if is_irrelevant:
            print(f"\n用户回答: {user_answer}")
            print(f"\n检测到无关回答: {reason}，按规则得0分")
            self.total_score += 0
            self.question_scores.append(0)
            print(f"\n评分: 0.0/10")
            print(f"反馈: 回答与题目或知识库内容无关，请围绕题目要求作答")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")
            messages.append({"role": "user", "content": user_answer})
            messages.append({"role": "assistant", "content": f"0|{reason}|否"})
            return

        if self._is_duplicating_question(self.current_question, user_answer):
            print(f"\n用户回答: {user_answer}")
            print("\n检测到回答只是重复或修改题目内容，按规则得0分")
            self.total_score += 0
            self.question_scores.append(0)
            print(f"\n评分: 0.0/10")
            print(f"反馈: 回答只是重复或修改题目内容，请提供具体分析和解释")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")
            messages.append({"role": "user", "content": user_answer})
            messages.append({"role": "assistant", "content": "0|回答只是重复或修改题目内容，未提供有效信息|否"})
            return

        answer_dimensions = self._extract_dimensions(user_answer)
        missing_dimensions = set(self.question_dimensions) - set(answer_dimensions)

        if missing_dimensions and len(answer_dimensions) > 0:
            print(f"\n⚠️ 回答未完全覆盖题目要求的维度（缺少: {', '.join(missing_dimensions)}）")
            dimension_penalty = len(missing_dimensions) / len(self.question_dimensions)
        else:
            dimension_penalty = 0

        messages.append({"role": "user", "content": user_answer})
        print(f"\n用户回答: {user_answer}")

        query = f"{self.current_question}\n{user_answer}"
        relevant_passages = self.retrieve_relevant_passages(query, top_k=3)
        context = "\n\n".join(relevant_passages)

        evaluation = self.get_ai_response(messages + [
            {"role": "system", "content": f"""
            请基于以下相关知识和评分标准进行评分：

            ===== 相关知识片段 =====
            {context[:2000]}

            ===== 评分标准 =====
            1. 知识库关键内容：{self.knowledge_content[:1000]}
            2. 题目要求维度：{', '.join(self.question_dimensions)}
            3. 评分标准：
               10分：全面覆盖所有维度，准确引用知识库内容，逻辑性非常强
               8-9分：覆盖主要维度，正确引用知识库内容，有一定深度，逻辑性较强
               6-7分：部分覆盖维度，与知识库有一定关联，内容较简单，逻辑性一般
               4-5分：提到基本概念，与题目相关但深度不足，或关联较弱，逻辑性差
               0分：未涉及题目核心内容或乱答，无逻辑性

            请按以下格式提供评分反馈：
                分数：[给出0-10分的具体分数]
                反馈：[详细说明评分理由，指出回答中的优缺点]
                改进建议：[提供具体的改进方向和学习建议]
            """},
        ])

        print(f"\n原始评分结果: {evaluation}")

        # 初始化默认值
        score = 6.0  # 默认分数
        feedback = "无法解析评分结果，按中等分数处理"
        suggestion = "请确保回答覆盖题目要求的所有维度"

        # 尝试从自由文本中提取分数和反馈
        try:
            # 匹配分数模式（支持整数和浮点数）
            score_match = re.search(r'(\d+(?:\.\d+)?)\s*分(?!钟)', evaluation)
            if score_match:
                score = float(score_match.group(1))
                # 确保分数在合理范围内
                score = max(0, min(10, score))

            # 提取反馈部分
            feedback_match = re.search(r'(反馈|评价|评语)[:：]?(.+?)(?=(改进建议|建议|$))', evaluation, re.DOTALL)
            if feedback_match:
                feedback = feedback_match.group(2).strip()

            # 提取改进建议
            suggestion_match = re.search(r'(改进建议|建议)[:：]?(.+)', evaluation, re.DOTALL)
            if suggestion_match:
                suggestion = suggestion_match.group(2).strip()

            print(f"成功解析评分: {score:.1f}分, 反馈: {feedback[:50]}...")
        except Exception as e:
            print(f"⚠️ 评分解析错误: {str(e)}")

        # 维度缺失惩罚处理
        if dimension_penalty > 0:
            score_deduction = min(2, dimension_penalty * 2)
            score = max(0, score - score_deduction)
            feedback = f"回答未完全覆盖所有维度（缺少: {', '.join(missing_dimensions)}），扣{score_deduction:.1f}分。{feedback}"
            print(f"维度缺失惩罚: 扣{score_deduction:.1f}分")

        # 更新总分和问题得分
        self.total_score += score
        self.question_scores.append(score)

        # 输出评分结果
        print(f"\n评分: {score:.1f}/10")
        print(f"反馈: {feedback}")
        print(f"改进建议: {suggestion}")
        print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")

        # 添加到消息历史
        messages.append({"role": "assistant", "content": f"{score:.1f}|{feedback}|{suggestion}"})

    def _ask_follow_up(self, messages: List[Dict]):
        pass

    def _show_final_score(self):
        """显示最终得分"""
        print("\n" + "=" * 50)
        if self.question_count > 0:
            percentage = (self.total_score / self.max_possible_score) * 100 if self.max_possible_score > 0 else 0

            print(f"\n考试结束！共完成 {self.question_count} 道题")
            print("\n各题得分明细:")
            for i, score in enumerate(self.question_scores, 1):
                print(f"第{i}题: {score:.1f}/10")

            print(f"\n最终得分: {self.total_score:.1f}/{self.max_possible_score} ({percentage:.1f}%)")

            if percentage >= 90:
                print("\n成绩卓越！全面掌握了知识库内容。")
            elif percentage >= 75:
                print("\n成绩优秀！对知识库内容有深入理解。")
            elif percentage >= 60:
                print("\n成绩良好！掌握了主要内容，细节有待加强。")
            elif percentage >= 40:
                print("\n成绩及格！需要加强知识库内容的学习。")
            else:
                print("\n未达标！建议系统复习知识库内容。")

            print("\n知识点掌握情况:")
            correct_counts = sum(1 for score in self.question_scores if score >= 6)
            print(f"- 掌握较好的知识点: {correct_counts}/{self.question_count}")
            print(f"- 需要加强的知识点: {self.question_count - correct_counts}/{self.question_count}")
        else:
            print("本次未完成任何题目。")
        print("=" * 50 + "\n")


if __name__ == "__main__":
    CONFIG = {
        "api_key": "sk-c501eb66620d47a9b1d1515e10598b47",
        "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "file_path": r"D:\桌面\竞赛\挑战杯+人工智能\第3章 数据获取.docx",
        "max_questions": 5
    }

    try:
        exam_bot = SmartExamBot(
            api_key=CONFIG["api_key"],
            base_url=CONFIG["base_url"],
            max_questions=CONFIG["max_questions"]
        )

        knowledge_content = exam_bot.read_file_content(CONFIG["file_path"])
        print(f"成功加载知识内容，长度: {len(knowledge_content)} 字符")
        print("提取的关键词:", ', '.join(exam_bot._extract_keywords(knowledge_content)))
        print("知识内容预览:", knowledge_content[:200] + "...")

        exam_bot.start_exam(knowledge_content)

    except Exception as e:
        print(f"程序运行出错: {str(e)}")
    finally:
        input("按回车键退出...")